#include "Array.hpp"
#include <assert.h>
#include <iostream>
#include <stdio.h>
#include <string>

using namespace std;
using namespace cs540;

//helper functions
static size_t caseIndex = 0;
void Log(const char* testcase){
	cout<<testcase<<endl;
}
void Log(const char* testcase, bool b){
	cout<<++caseIndex<<". "<<testcase<<'\t'<<(b?"PASS":"FAIL")<<endl;
}

class A{
	friend void doit();
public:
	int i;
	A(){}
	A(int i_) : i(i_) {}
	friend ostream& operator << (ostream& os, const A& a) { os<<a.i; return os;}
};

class B : public A{
	friend void doit();
public:
	int j;
	B(){}
	B(int j_) : j(j_) {}
	B(A& a): j(a.i) {}
	friend ostream& operator << (ostream& os, const B& b) { os<<b.j; return os;}
	B& operator = (const A& a){j=a.i;return *this;}
};

void doit();
int main(int argc, char* argv[]) 
{
	doit();
}

void doit()
{
	//test group 1: array instantiation with different types
	//related member function: 
	//	1. Array(size_t (&)[D]);
	//pass condition: no crash, no memory error/leak in valgrind, can be correctly used in testcases later
	//-----------------------------------------------------------------------

	//Yibo: moved the definition and instantiation statements with the test cases on them
	size_t dim1[] = {1};
	size_t dim2[] = {1, 2};
	size_t dim3[] = {2, 3, 4};
	size_t dim4[] = {2, 3, 4, 5};

	//test group 2: index operator
	//related member function: 
	//	1. T &operator[size_t i_1][size_t i_2]...[size_t i_D];
	//	2. const T &operator[size_t i_1][size_t i_2]...[size_t i_D] const;
	//-----------------------------------------------------------------------

	//1x1 array
	{
		Array<1, int> a1(dim1);
		int i = 2;
		a1[0] = i;
		Log("a1[0] = i;", a1[0]==i);
	}	

	//2-dimensional array, but only one row (or column)
	//test template with reference
	{
		Array<2, string> a2(dim2);
		const char* str = "hello world";
		const char* str2 = "hello what";

		string s(str);
		a2[0][0] = s;
		Log("a2[0][0] = s;", 0==a2[0][0].compare(str));

		a2[0][1] = a2[0][0];
		Log("a2[0][1] = a2[0][0]", 0==a2[0][1].compare(a2[0][0]));
	}
	
	//test template with pointer
	{
		Array<3, int*> a3(dim3);
		int i = 11;
		a3[0][0][0] = &i;
		Log("a3[0][0][0] = &i;", *a3[0][0][0]==i);

		*a3[0][0][0] += 2;
		Log("*a3[0][0][0] += 2;", i==13);

		a3[1][2][3] = &i;
		Log("a3[1][2][3] = &i;", *a3[1][2][3]==13);
	}

	//test template with object
	{
		Array<4, A> a4(dim4);
		A o1(17);
		a4[0][0][0][0] = o1;
		Log("a4[0][0][0][0] = o1;", a4[0][0][0][0].i==17);

		a4[1][2][3][4] = a4[0][0][0][0];
		Log("a4[1][2][3][4] = a4[0][0][0][0];", a4[1][2][3][4].i==17);
	}

	//test index out of range
	//PASS condition: throw an exception of type OutOfRange
	{
		Array<3, int> arr(dim3);

		//first dimension
		try{
			arr[2][0][1] = 3;
		}
		catch(OutOfRange e){
			Log("arr[2][0][1] = 3;", true);
		}
		catch(...){
			Log("arr[2][0][1] = 3;", false);
		}

		//second dimension
		try{
			arr[0][3][1] = 3;
		}
		catch(OutOfRange e){
			Log("arr[0][3][1] = 3;", true);
		}
		catch(...){
			Log("arr[0][3][1] = 3;", false);
		}
		
		//third dimension
		try{
			arr[1][2][4] = 3;
		}
		catch(OutOfRange e){
			Log("arr[1][2][4] = 3;", true);
		}
		catch(...){
			Log("arr[1][2][4] = 3;", false);
		}
	}

	//test with large dimensions
	{
		size_t dim10[] = {2, 3, 2, 3, 2, 3, 2, 3, 2, 3};
		Array<10, int> a10(dim10);
		//Array<100, int> a10(dim10);

		const int i = 2010;
		a10[0][0][0][0][0][0][0][0][0][0] = i;
		Log("a10[0][0][0][0][0][0][0][0][0][0] = i", a10[0][0][0][0][0][0][0][0][0][0] == i);

		a10[1][2][1][2][1][2][1][2][1][2] = a10[0][0][0][0][0][0][0][0][0][0] + 1;
		Log("a10[1][2][1][2][1][2][1][2][1][2] = a10[0][0][0][0][0][0][0][0][0][0] + 1", a10[1][2][1][2][1][2][1][2][1][2] == i+1);
	}

	//test group 3: copy constructor
	//related member functions:
	//	1. Array(const Array&)
	//	2. template <typename U> Array(const Array<D, U> &)
	//--------------------------------------------------------------------
	{
		int i = 11;
		Array<3, int> a3(dim3);
		a3[1][2][3] = i;

		//same type
		Array<3, int> a3copy(a3);
		Log("Array<3, int> a3copy(a3);", a3copy[1][2][3]==i);

		Array<3, long> a3copy2(a3);
		Log("Array<3, long> a3copy2(a3);", a3copy2[1][2][3]==i);

		//yibo: bad test case
		//with temp object
		//i = 17;
		//Array<3, int> a3temp(Array<3, int>);
		//a3temp[1][2][3] = i;
		//Log("Array<3, int> a3temp(Array<3, int>);", a3temp[1][2][3]==i);

		//make sure they have same dimension
		//first dimension
		try{
			a3copy[2][0][0] = 1;
		}
		catch(OutOfRange e){
			Log("a3copy[2][0][0] = 1;", true);
		}
		catch(...){
			Log("a3copy[2][0][0] = 1;", false);
		}

		//second dimension
		try{
			a3copy[0][3][0] = 3;
		}
		catch(OutOfRange e){
			Log("a3copy[0][3][0] = 3;", true);
		}
		catch(...){
			Log("a3copy[0][3][0] = 3;", false);
		}
		
		//third dimension
		try{
			a3copy[1][2][4] = 3;
		}
		catch(OutOfRange e){
			Log("a3copy[1][2][4] = 3;", true);
		}
		catch(...){
			Log("a3copy[1][2][4] = 3;", false);
		}
}

	//test group 4: assignment
	//related member functions:
	//	1. Array & operator=(const Array &);
	//	2. template <typename U> Array& operator=(cosnt Array<U> &);
	{
		int i = 19;
		Array<3, int> a3(dim3);
		Array<3, int> a4(dim3);
		a3[1][2][3] = i;
		a4[1][2][3] = -1*i;

		a4 = a3;
		Log("a4 = a3;", a4[1][2][3]==i);

		//make sure they have same dimension
		//first dimension
		try{
			a4[2][0][0] = 1;
		}
		catch(OutOfRange e){
			Log("a4[2][0][0] = 1;", true);
		}
		catch(...){
			Log("a4[2][0][0] = 1;", false);
		}

		//second dimension
		try{
			a4[0][3][0] = 3;
		}
		catch(OutOfRange e){
			Log("a4[0][3][0] = 3;", true);
		}
		catch(...){
			Log("a4[0][3][0] = 3;", false);
		}
		
		//third dimension
		try{
			a4[1][2][4] = 3;
		}
		catch(OutOfRange e){
			Log("a4[1][2][4] = 3;", true);
		}
		catch(...){
			Log("a4[1][2][4] = 3;", false);
		}
	}

	//assignment with different types
	{
		Array<3, int> a3int(dim3);
		Array<3, double> a3double(dim3);
		a3int[0][0][0] = 100;
		a3double[0][0][0] = -1.;
		a3double = a3int;
		Log("a3double = a3int;", a3double[0][0][0]>0);
	}

	//2. template<typename U> Array& operator=(const Array<U> &)
	{
		Array<3, A> a3(dim3);
		a3[0][1][2].i = 1234;
		Array<3, B> b3(dim3);
		b3 = a3;
		
		Log("Array<3, B> a3 = Array<3, A>(dim3);", b3[0][1][2].j == 1234);
	}

	//self assignment should be no-op
	//PASS condition: since it's no-op, the code block should execute quickly and no extra memory consumption
	{
		Log("start self assignment...");
		char* buf = new char[1000000];
		Array<1, char*> a1(dim1);
		a1[0] = buf;
		for(size_t i=0;i<1000000;++i)
			a1 = a1;
		delete [] buf;
		Log("done with self assignment.");
	}

	//test group: constantness
	//PASS condition: line Lxx can't compile
	{
		Array<3, int> a3(dim3);
		const Array<3, int>& a3ConstRef = a3;
		a3[0][0][0] == 0;
		//a3ConstRef[0][0][0] = 1; //Lxx
		Log("constantess.", true);
	}
}
